昨日完成了產生 NFT QRcode 的部分,今天起三天我想要來設計出一個驗證系統,主要是透過前端處理後傳入區塊鏈,可以透過鏈上的合約來驗證,確認後將原票卷銷毀(burn),再 mint 一張紀念票卡給使用者(這部分會在後面討論這樣做的原因)。
前端負責的流程如下:
大致瞭解了驗證的流程後,便可以開始前端的冒險了 (?)。
在將我們拿到的資料轉換成 QRCode 之前,我們會先將這筆資料做預處理。昨天雖然在程式碼中有寫到但是卻沒有提到,這是因為尚未想好整體的驗證流程。
首先我想要拿來處理得資料就是使用者的 address,不要懷疑就是 address!
原因是這樣的,因為在前面 fetch 資料時已經確認過使用者的 address 是在特定 tokenId
裡面的 owner
了,因此可以確認說使用者是確實擁有這個 ticket。此外我們還會利用加點東西再 hash()
,因此就安全性來說應該是可行的(如果不可行可以來證明XD)。
這邊我想加入 "token3_audience_tickets"
,與 address 一起進行 keccak256()
,但很快的我就發現了一些問題:
我原本這樣寫:
const addedMessage = "token3_audience_tickets";
const addedAddress = accounts[0];
const QrData = keccak256(addedMessage, addedAddress);
log 在開發者工具中可以看到其實他會轉換成一個 Uint8 Array
:
Uint8Array(32) [
196, 238, 197, 42, 97, 187, 55, 41, 83, 198,
11, 84, 211, 172, 190, 28, 147, 138, 196, 66,
84, 241, 118, 201, 232, 55, 80, 151, 47, 77,
102, 231,
buffer: ArrayBuffer(32),
byteLength: 32,
byteOffset: 0,
length: 32,
Symbol(Symbol.toStringTag): 'Uint8Array']
將他使用 buf2hex()
來轉換可以變成:
const buf2hex = (buffer) => { // buffer is an ArrayBuffer
return ["0x", ...new Uint8Array(buffer)]
.map(x => x.toString(16).padStart(2, '0'))
.join('');
}
console.log(buf2hex(QrData));
得到:
0xc4eec52a61bb372953c60b54d3acbe1c938ac44254f176c9e83750972f4d66e7
用十六進制轉換 196 可得到 c4
、238 可以得到 ee
...,可以驗證他是正確的轉換過去。
這時候有點小疑惑發生了,我要怎麼保證我 hash 的結果可以跟鏈上的結果一樣?於是我就又做了下面這件事來確認一下。
在 solidity
上使用 keccak256()
:
function getKeccak256() public pure returns(bytes32) {
string memory a = "token3_audience_tickets";
address b = 0xaB50035F25F96dd3DEf1A7a9cC4E1C81AD6a7651;
return (keccak256(abi.encodePacked(a,b)));
}
會得到結果:
0xf3f978628bf8dc3da706e75c320c1c8521579aadb64c9acb5d7d085844615d30
這時組長眉頭一皺。
如果鏈下與鏈上的 hash 結果不同不就沒辦法達到驗證的效果了嗎?
因此我仔細思索後發現了一個問題,在 solidity 中會由 abi.encodePacked()
這個函式先處理過兩個變數之後才會進行,之前有提到這個函式,但沒有深入了解。
根據官方 doc上寫的,abi.encodePacked()
可以把丟進去的變數轉換成 bytes32
,並按照順序的將他們排列。
直接來舉例就行了:
function getEncoded() public pure returns(bytes memory) {
uint16 x = 12;
bytes1 a = 0x42;
int16 b = -1;
return (abi.encodePacked(x, a, b));
}
我寫了一個函式來查看 abi.encodePacked()
回傳的值,最後可以得到:
0x000c42ffff |
^^^^^^ | uint16(12)
^^ | bytes1(0x42)
^^^^ | int16(-1)
可以看到這些變數都按照順序的變成 bytes32
形式來表示了。
了解 abi.encodePacked()
後事情就好做了,我只要做到將這些變數都轉成 bytes32
就行了吧?
但是事情不是憨人所想那麼簡單,我找尋了頗多資料後發現,可以使用 Node.js 內建的 Buffer library 來做到相同的事情,這篇 Stack Overflow 有解釋,大致上的做法就是將資料轉換成 hex 形式的 string,我們再透過 string.concat()
的做法將他們 pack 起來。
但懶人如我想要找尋更簡單的方法:一鍵 encode。
事實上 web3 本身就有提供一個函式可以幫助我們做到上述的事情:
web3.utils.encodePacked(addedMessage, addedAddress)
const hashedMessage = keccak256(encodePackedMessage);
最後可以得到:
0xf3f978628bf8dc3da706e75c320c1c8521579aadb64c9acb5d7d085844615d30
如此一來就可以在鏈上也同時驗證啦!
今天雖然對於專案的進度很少,但是對我的基礎知識又更加深了一點,以前搞不懂的 byte、bytes,現在已經更清楚一點了。另外也搞懂了我一直以來不了解的 abi.encodePacked()
的真正用途是什麼,以及他 encode 的方式,其實獲益良多!
明天會將 QRCode 掃描並取得其中的資料,並在這個頁面中同時做驗證!